from apps.storage.models.file import ObjectFileType, ObjectContentType, ObjectFile
from tempfile import mkdtemp,NamedTemporaryFile
import os
from .serializers.excel import ExcelHandler
from .serializers.xml import XMLHandler
from .serializers.json import JSONSerializer
from .serializers.common import DataMode
import shutil
from tempfile import TemporaryFile
from patoolib import extract_archive
from patoolib.util import PatoolError


def save_data_file(template, queryset,
                   o_type: ObjectFileType, c_type: ObjectContentType, **kwargs):
    """
    保存数据/数据集文件
    :param template: 所使用的模板
    :param queryset: 所针对的数据查询集
    :param o_type: 对象类型（DATA/DATASET）
    :param c_type: 文件类型
    :param kwargs: DATA 需设置 oid
    :return: 返回保存后的文件对象
    """
    tmp_dir = mkdtemp()
    tmp_file_path = os.path.join(tmp_dir, 'tmp.file')
    if c_type.is_xlsx:
        maker = ExcelHandler(mode=DataMode.WRITE, template=template)
        for idx, dm in enumerate(queryset):
            maker.write_data(dm, short_data_id='DATA' + str(idx + 1))
        maker.save(tmp_file_path)
    elif c_type.is_xml:
        maker = XMLHandler(mode=DataMode.WRITE, template=template)
        for idx, dm in enumerate(queryset):
            maker.write_data(dm, short_data_id='DATA' + str(idx + 1))
        maker.save(tmp_file_path)
    elif c_type.is_json:
        JSONSerializer.serialize(template, queryset, tmp_file_path)
    # 成功之后保存到 tdf 最后删除临时文件
    with open(tmp_file_path, 'rb') as tmp_file:
        tdf = ObjectFile.add(o_type, c_type, tmp_file, **kwargs)  # TODO：dataset 打包压缩包？

    shutil.rmtree(tmp_dir)
    return tdf


def _save_xlsx(template):
    tmp_file = TemporaryFile()
    maker = ExcelHandler(mode=DataMode.TEMPLATE, template=template)
    maker.generate_template()
    maker.save(tmp_file)
    tmp_file.seek(0)
    return tmp_file


def _save_xml(template):
    tmp_file = TemporaryFile()
    maker = XMLHandler(mode=DataMode.TEMPLATE, template=template)
    maker.generate_template()
    maker.save(tmp_file)
    tmp_file.seek(0)
    return tmp_file


def _save_json(template):
    tmp_file = TemporaryFile()
    dummy = JSONSerializer.serialize(template)
    tmp_file.write(dummy.encode('utf-8'))
    tmp_file.seek(0)
    return tmp_file


def save_template_file(template, c_type: ObjectContentType):
    """
    保存模板文件，json/xml/xlsx 三种
    :param template: 模板对象
    :param c_type: 文件类型
    :return: 返回保存得到的对象文件
    :exception: 不作异常处理，可能出现 Template.DoesNotExist, KeyError 等
    """
    if c_type.is_xlsx:
        tmp_file = _save_xlsx(template)
    elif c_type.is_xml:
        tmp_file = _save_xml(template)
    elif c_type.is_json:
        tmp_file = _save_json(template)
    else:
        raise KeyError
    # 上面如果被正确处理的话就保存临时文件到 ptf
    of = ObjectFile.add(ObjectFileType.TEMPLATE, c_type, tmp_file, tid=template.id)
    return of


def _get_true_archived_file(archived_file, suffix_expected=('.JSON', '.XLSX', '.XML')):
    """解压压缩包到临时目录，找到期望获取的文件后返回(压缩包路径,该文件的路径,后缀名）"""
    tmp_dir = mkdtemp()
    arc_f_path = os.path.join(tmp_dir, os.path.basename(archived_file.name))

    with open(arc_f_path, 'wb') as f:
        while True:
            data = archived_file.read(1024)
            if not data:
                break
            f.write(data)

    o_dir = os.path.join(tmp_dir, '_o')
    os.mkdir(o_dir)  # 创建一个输出目录

    try:
        extract_archive(arc_f_path, outdir=o_dir)
    except PatoolError as ex:
        shutil.rmtree(o_dir)
        raise ValueError(str(ex))

    child_files = os.listdir(o_dir)  # 防止出现多级文件夹的情况
    while len(child_files) == 1 and os.path.isdir(os.path.join(o_dir, child_files[0])):
        o_dir = os.path.join(o_dir, child_files[0])
        child_files = os.listdir(o_dir)

    child_f = None
    child_f_ext = None
    for child_f in child_files:
        child_f_ext = os.path.splitext(child_f)[1]
        child_f_ext = child_f_ext.upper()
        if child_f_ext in suffix_expected:
            break
        child_f_ext = None

    if not child_f_ext or not child_f:
        shutil.rmtree(o_dir)
        raise ValueError('Cannot find expected %s file in archive.' % str(suffix_expected))

    return arc_f_path, os.path.join(o_dir, child_f), child_f_ext


def save_uploaded_file(c_type: ObjectContentType, file, author):
    if c_type.is_others:
        arc_f_path, f_path, f_ext = _get_true_archived_file(file)
        if f_ext == '.JSON':
            JSONSerializer.deserialize(f_path)
        elif f_ext == '.XML':
            maker = XMLHandler(mode=DataMode.READ)
            maker.read_data(f_path,
                            file_dir=os.path.dirname(os.path.abspath(f_path)),
                            author=author)
        elif f_ext == '.XLSX':
            maker = ExcelHandler(mode=DataMode.READ)
            maker.read_data(f_path,
                            file_dir=os.path.dirname(os.path.abspath(f_path)),
                            author=author)
        shutil.rmtree(os.path.dirname(arc_f_path))  # 全部操作结束后删除所有临时数据
    else:
        tmp_file = NamedTemporaryFile()
        tmp_file.write(file.read())  # 先保存到临时文件
        tmp_file.seek(0)
        if c_type.is_xlsx:
            maker = ExcelHandler(mode=DataMode.READ)
            maker.read_data(tmp_file, author=author)
        elif c_type.is_xml:
            maker = XMLHandler(mode=DataMode.READ)
            maker.read_data(tmp_file, author=author)
        elif c_type.is_json:
            JSONSerializer.deserialize(tmp_file)
        os.unlink(tmp_file.name)
    # 前面操作完成后可以保存上传的文件
    file.seek(0)
    of = ObjectFile.add(ObjectFileType.UPLOADED, c_type, file, name=file.name, author=author)
    return of


class ObjectFileMaker:
    def __init__(self, o_type: ObjectFileType, c_type: ObjectContentType):
        self._o_t = o_type
        self._c_t = c_type

    def save(self, template=None, file=None, queryset=None, oid=None, author=None, **kwargs):
        if self._o_t.is_data or self._o_t.is_dataset:
            return save_data_file(template=template,
                                  queryset=queryset,
                                  o_type=self._o_t, c_type=self._c_t,
                                  oid=oid)
        elif self._o_t.is_template:
            return save_template_file(template=template,
                                      c_type=self._c_t)
        elif self._o_t.is_uploaded:
            return save_uploaded_file(self._c_t, file=file, author=author)

'''
lyp add
'''
def update_template_file(template, c_type: ObjectContentType=None):
    """
    更新模板文件，可以在模板 patch 时触发，或者定期更新
    :param template: 所需要更新的模板
    :param c_type: 对象文件类型
    :return: 返回更新后的文件对象
    """
    if c_type is None:
        update_template_file(template, ObjectContentType.JSON)
        update_template_file(template, ObjectContentType.XML)
        update_template_file(template, ObjectContentType.XLSX)
        return

    of = ObjectFile.objects.filter(misc__tid=template.id, c_type=c_type).first()
    if of is None:
        save_template_file(template, c_type)    # 尚未创建模板则创建
    else:
        if c_type.is_json:
            tmp_file = _save_json(template)
        elif c_type.is_xml:
            tmp_file = _save_xml(template)
        elif c_type.is_xlsx:
            tmp_file = _save_xlsx(template)
        else:
            raise KeyError

        origin_file_path = of.file.path
        f_name = ObjectFile.get_short_url() + ObjectContentType.get_suffix(c_type)
        of.file.save(f_name, tmp_file)
        of.save(update_fields=('file',))
        try:
            os.remove(origin_file_path)     # 创建成功则删除旧文件
        except FileNotFoundError:
            pass
